package com.motu.monstercity.server.game.logic;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.motu.monstercity.module.common.redis.RedisKey;
import com.motu.monstercity.protocol.Common.PBUserData;
import com.motu.monstercity.server.game.base.AllParam;
import com.motu.monstercity.server.game.base.CommonUtils;
import com.motu.monstercity.server.game.base.GameConstant;
import com.motu.monstercity.server.game.base.ProtoDataUtils;
import com.motu.monstercity.server.game.commondata.bag.ItemId;
import com.motu.monstercity.server.game.commondata.event.EventMain;
import com.motu.monstercity.server.game.commondata.fight.Stage;
import com.motu.monstercity.server.game.commondata.partner.PartnerMain;
import com.motu.monstercity.server.game.commondata.secretary.SecretaryMain;
import com.motu.monstercity.server.game.commondata.system.UserExp;
import com.motu.monstercity.server.game.log.LogType;
import com.motu.monstercity.server.game.userdata.*;
import com.motu.vertx.module.utility.redis.RedisTools;
import com.motu.vertx.module.utility.toolset.ErrorWord;
import com.motu.vertx.module.utility.toolset.TimeUtils;
import com.motu.vertx.module.utility.toolset.Tool;
import io.vertx.core.Future;
import io.vertx.core.json.JsonArray;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public class EventManager extends EventUserData {

    public static void initUserData(UserExtInfo userExtInfo, PBUserData.Builder pbUserData) {
//        checkDelOnlineEvent(userExtInfo);
        long userId = userExtInfo.getId();
        List<UserOnlineEvent> userOnlineEventList = getUserOnlineEventList(userId);
        if (!userOnlineEventList.isEmpty()) {
            for (UserOnlineEvent userOnlineEvent : userOnlineEventList) {
                ProtoDataUtils.updatePBUserData(pbUserData, userOnlineEvent, true);
            }
        }
        List<UserEvent> userEventList = getUserEventList(userId);
        if (!userEventList.isEmpty()) {
            for (UserEvent userEvent : userEventList) {
                ProtoDataUtils.updatePBUserData(pbUserData, userEvent, true);
            }
        }
        UserOnlineEventInfo userOnlineEventInfo = getUserOnlineEventInfo(userId);
        ProtoDataUtils.updatePBUserData(pbUserData, userOnlineEventInfo, true);
    }

    // 时间缓存到内存，避免频繁读取
    private static final Cache<Long, Long> eventTimeCache = CacheBuilder.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES).build();
    // 任务数缓存到内存
    private static final Cache<Long, Long> eventCountCache = CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build();
    // 玩家等级缓存
    private static final Cache<Long, Integer> userLevelCache = CacheBuilder.newBuilder().expireAfterAccess(8, TimeUnit.MINUTES).build();

    private static long getCachedEventTime(long userId, Queue<UserOnlineEventInfo> eventInfoQueue) {
        Long eventTime = eventTimeCache.getIfPresent(userId);
        if (eventTime == null) {
            UserOnlineEventInfo userOnlineEventInfo = getUserOnlineEventInfo(userId);
            eventTime = userOnlineEventInfo.getLastEventTime();
            eventTimeCache.put(userId, eventTime);
            eventInfoQueue.add(userOnlineEventInfo);
        }
        return eventTime;
    }

    private static long getCachedEventCount(long userId) {
        Long eventCount = eventCountCache.getIfPresent(userId);
        if (eventCount == null) {
            eventCount = getOnlineEventCount(userId);
            eventCountCache.put(userId, eventCount);
        }
        return eventCount;
    }

    private static int getCachedUserLevel(long userId) {
        Integer userLevel = userLevelCache.getIfPresent(userId);
        if (userLevel == null) {
            UserInfo userInfo = GameUser.getUserInfo(userId);
            userLevel = userInfo.getLevel();
            userLevelCache.put(userId, userLevel);
        }
        return userLevel;
    }

    // 生成在线随机事件
    public static void randomOnlineEvent(UserInfo userInfo, PBUserData.Builder pbUserData) {
        long userId = userInfo.getId();
        try {
            boolean result = Future.await(RedisTools.setnx(RedisKey.getEventLockKey(userId), "1"));
            if (result) {
                UserExp userExp = GameDataManager.getUserExp(userInfo.getLevel());
                if (userExp == null || userExp.eventNumArray.length < 2) {
                    return;
                }
                int groupId = userExp.eventNumArray[0];
                int eventNum = userExp.eventNumArray[1];
                Queue<UserOnlineEventInfo> eventInfoQueue = new LinkedList<>();
                if (TimeUtils.getTimeDiff(getCachedEventTime(userId, eventInfoQueue)) < AllParam.EVENT_TRIGGER_CD - 1) { // 未到触发时间（1秒误差）
                    return;
                }
                if (getCachedEventCount(userId) >= eventNum) {
                    return;
                }

                List<EventMain> onlineEventList = getEventMainTriggerMap().get(groupId);
                if (onlineEventList != null && !onlineEventList.isEmpty()) {
                    List<EventMain> pool = new ArrayList<>(onlineEventList);
                    int index = Tool.getIntRandom(pool.size());
                    EventMain eventMain = pool.get(index);
                    UserOnlineEvent userOnlineEvent = new UserOnlineEvent(userId, eventMain.getId());
                    userOnlineEvent.putStartTime(TimeUtils.getCurTime());
                    userOnlineEvent.doCreate();
                    ProtoDataUtils.updatePBUserData(pbUserData, userOnlineEvent, true);
                    UserOnlineEventInfo userOnlineEventInfo = eventInfoQueue.isEmpty() ? getUserOnlineEventInfo(userId) : eventInfoQueue.remove();
                    userOnlineEventInfo.putLastEventTime(TimeUtils.getCurTime());
                    userOnlineEventInfo.update();
                    ProtoDataUtils.updatePBUserData(pbUserData, userOnlineEventInfo);
                    eventTimeCache.put(userId, userOnlineEventInfo.getLastEventTime()); // 本次直接更新时间，下次就不需要重新读取玩家数据了
                    eventCountCache.invalidate(userId); // 下次调用getEventCount方法时重新读取redis
                }
            }
        } finally {
            boolean success = Future.await(RedisTools.del(RedisKey.getEventLockKey(userId)));
        }
    }

    // 离线时间超过一小时（读取配置表）后删除所有事件
    public static void checkDelOnlineEvent(PBUserData.Builder pbUserData, UserInfo userInfo) {
        long userId = userInfo.getId();
        try {
            boolean result = Future.await(RedisTools.setnx(RedisKey.getEventLockKey(userId), "1"));
            if (result) {
                List<UserOnlineEvent> userOnlineEventList = getUserOnlineEventList(userId);
                if (!userOnlineEventList.isEmpty()) {
                    for (UserOnlineEvent userOnlineEvent : userOnlineEventList) {
                        userOnlineEvent.delete();
                        ProtoDataUtils.deletePBUserData(pbUserData, userOnlineEvent);
                    }
                }
                UserExp userExp = GameDataManager.getUserExp(userInfo.getLevel());
                if (userExp == null || userExp.eventNumArray.length < 2) {
                    return;
                }
                createOnlineEvent(pbUserData, userId, userExp.eventNumArray[0], userExp.eventNumArray[1]);
            }
        } finally {
            boolean result = Future.await(RedisTools.del(RedisKey.getEventLockKey(userId)));
        }
    }

    public static void createOnlineEvent(PBUserData.Builder pbUserData, long userId, int eventGroup, int maxNum) {
        List<EventMain> onlineEventList = getEventMainTriggerMap().get(eventGroup);
        if (onlineEventList == null || onlineEventList.isEmpty()) {
            return;
        }
        long triggerTime = TimeUtils.getCurTime();
        List<EventMain> pool = new ArrayList<>(onlineEventList);
        for (int i = 0; i < maxNum; i++) {
            if (pool.isEmpty()) {
                return;
            }
            int index = Tool.getIntRandom(pool.size());
            EventMain eventMain = pool.get(index);
            UserOnlineEvent userOnlineEvent = new UserOnlineEvent(userId, eventMain.getId());
            userOnlineEvent.putStartTime(triggerTime);
            userOnlineEvent.doCreate();
            ProtoDataUtils.updatePBUserData(pbUserData, userOnlineEvent, true);
            pool.remove(index);
            triggerTime += AllParam.EVENT_TRIGGER_CD;
        }
        UserOnlineEventInfo userOnlineEventInfo = getUserOnlineEventInfo(userId);
        userOnlineEventInfo.putLastEventTime(triggerTime);
        userOnlineEventInfo.update();
        eventTimeCache.put(userId, userOnlineEventInfo.getLastEventTime()); // 本次直接更新时间，下次就不需要重新读取玩家数据了
        eventCountCache.invalidate(userId); // 下次调用getEventCount方法时重新读取redis
    }

    /**
     * 通过外部调用更新事件数量，该方法在领取奖励或一键领取奖励后调用
     */
    public static void refreshEventCount(long userId) {
        if (eventCountCache.getIfPresent(userId) != null) {
            eventCountCache.invalidate(userId);
        }
    }

    /**
     * 通过外部调用更新玩家等级缓存，该方法在升级时调用
     */
    public static void refreshUserLevel(long userId) {
        if (userLevelCache.getIfPresent(userId) != null) {
            userLevelCache.invalidate(userId);
        }
    }

    /**
     * 触发普通事件
     */
    public static void triggerNormalEvent(UserInfo userInfo, Stage stage, PBUserData.Builder pbUserData) {
        int eventId = stage.getEvent();
        if (eventId == 0) {
            return;
        }
        List<EventMain> eventlist = new ArrayList<>();
        if (eventId < 10000) {
            List<EventMain> list = getEventMainList(eventId);
            eventlist.addAll(list);
        } else {
            EventMain eventMain = getEventMain(eventId);
            if (null != eventMain) {
                eventlist.add(eventMain);
            }
        }
        if (eventlist.isEmpty()) {
            return;
        }
        UserOnlineEventInfo userOnlineEventInfo = getUserOnlineEventInfo(userInfo.getId());
        int[] weights = new int[eventlist.size()];
        int lastGetStaff = userOnlineEventInfo.getLastGetStaff();
        for (int i = 0; i < eventlist.size(); i++) {
            EventMain eventMain = eventlist.get(i);
            weights[i] = 1; // 所有事件等概率，暂定所有事件权重都是1
            // 派遣事件如果关键人员是最后获得的职员则概率提升1倍（参数表配置）
            if (eventMain.getEventType() == EventMain.EVENT_TYPE_DISPATCH && lastGetStaff == eventMain.getKeyman()) {
                weights[i] = weights[i] * AllParam.EVENT_KEYMAN_RATE / 10000;
            }
        }
        int index = Tool.getWeightIndex(weights);
        EventMain eventMain = eventlist.get(index);
        long userId = userInfo.getId();
        UserEvent userEvent = new UserEvent(userId, eventMain.getId());
        userEvent.doCreate();
        ProtoDataUtils.updatePBUserData(pbUserData, userEvent, true);
    }

    public static Object recvOnlineEvent(UserInfo userInfo, int eventId, PBUserData.Builder pbUserData, int group) {
        EventMain eventMain = getEventMainMap().get(eventId);
        if (eventMain == null) {
            return ErrorWord.NOT_DATA_2;
        }
        List<RewardItem> rewardItemList = CommonUtils.takeReawrdItemFromStr(eventMain.getReward());
        if (eventMain.getEventType() == EventMain.EVENT_TYPE_DISPATCH) { // 派遣事件算的是倍数奖励
            int rewardPerson = eventMain.getRewardPerson(0);
            if (group > 0) { // 有倍数加成就乘以相应的倍数
                CommonUtils.multipleItemList(rewardItemList, group + 1);
                rewardPerson *= group + 1;
            }
            RewardManager.addReward(userInfo, rewardItemList, pbUserData, LogType.ONLINE_EVENT_REWARD, eventId, group);
            if (rewardPerson > 0) {
                RewardManager.addReward(userInfo, ItemId.PERSON, rewardPerson, pbUserData, LogType.ONLINE_EVENT_REWARD, eventId, group);
            }
        } else {
            if (group == 0) { // 不需要选择的直接把配置的奖励下发
                RewardManager.addReward(userInfo, rewardItemList, pbUserData, LogType.ONLINE_EVENT_REWARD, eventId);
                int rewardPerson = eventMain.getRewardPerson(0);
                if (rewardPerson > 0) {
                    RewardManager.addReward(userInfo, ItemId.PERSON, rewardPerson, pbUserData, LogType.ONLINE_EVENT_REWARD, eventId);
                }
            } else { // 需要选择的把选择的奖励下发
                if (rewardItemList.size() >= group) {
                    RewardItem chooseReward = rewardItemList.get(group - 1);
                    RewardManager.addReward(userInfo, chooseReward, pbUserData, LogType.ONLINE_EVENT_REWARD, eventId, group);
                }
                int rewardPerson = eventMain.getRewardPerson(group - 1);
                if (rewardPerson > 0) {
                    RewardManager.addReward(userInfo, ItemId.PERSON, rewardPerson, pbUserData, LogType.ONLINE_EVENT_REWARD, eventId, group);
                }
            }
        }
        return null;
    }

    /**
     * 获取满足条件的参数
     */
    public static int getMatchParams(EventMain eventMain, List<Integer> param, long userId) {
        JsonArray eventParamArr = eventMain.getEventParamArr();
        JsonArray newEventParamArr = new JsonArray();
        newEventParamArr.addAll(eventParamArr);
        int matchCount = 0;
        List<UserSecretary> secretaryList = SecretaryManager.getUserSecretaries(userId);
        Map<Integer, UserSecretary> secretaryMap = secretaryList.stream().collect(Collectors.toMap(UserSecretary::getSecretaryId, s -> s));
        List<UserPartner> partnerList = PartnerManager.getUserPartnerList(userId);
        Map<Integer, UserPartner> partnerMap = partnerList.stream().collect(Collectors.toMap(UserPartner::getPartnerId, s -> s));
        for (int i = 0; i < param.size(); i++) {
            int staffId = param.get(i);
            for (int j = 0; j < newEventParamArr.size(); j++) {
                JsonArray eventParams = newEventParamArr.getJsonArray(j);
                int needType = eventParams.getInteger(0);
                int needParam = eventParams.getInteger(1);
                if (matchParam(needType, needParam, staffId, secretaryMap, partnerMap)) {
                    matchCount++;
                    newEventParamArr.remove(j);
                    break;
                }
            }
        }
        return matchCount;
    }

    public static boolean matchParam(int needType, int needParam, int staffId, Map<Integer, UserSecretary> secretaryMap, Map<Integer, UserPartner> partnerMap) {
        int type = GameConstant.getItemType(staffId);

        if (type == GameConstant.REWARD_TYPE_SECRETARY) {
            UserSecretary userSecretary = secretaryMap.get(staffId);
            if (userSecretary == null) {
                return false;
            }
            SecretaryMain secretaryMain = SecretaryManager.getSecretaryMain(staffId);
            if (secretaryMain == null) {
                return false;
            }
            if (needType == 2) {
                return needParam == staffId;
            } else if (needType == 1) {
                if (needParam > 0) {
                    return needParam == secretaryMain.getDisposition();
                } else {
                    return true;
                }
            } else {
                return false;
            }
        } else if (type == GameConstant.REWARD_TYPE_PARTNER) {
            UserPartner userPartner = partnerMap.get(staffId);
            if (userPartner == null) {
                return false;
            }
            PartnerMain partnerMain = PartnerManager.getPartnerMain(staffId);
            if (partnerMain == null) {
                return false;
            }
            if (needType == 4) {
                return needParam == staffId;
            } else if (needType == 3) {
                if (needParam > 0) {
                    return needParam == partnerMain.getCharacterType();
                } else {
                    return true;
                }
            } else {
                return false;
            }
        }
        return false;
    }

    /**
     * 更新最后获得的职员，用于计算职员事件权重
     */
    public static void updateKeyMan(long userId, int staffId) {
        UserOnlineEventInfo userOnlineEventInfo = getUserOnlineEventInfo(userId);
        userOnlineEventInfo.putLastGetStaff(staffId);
        userOnlineEventInfo.update();
    }

}
